<html>
    <head>
        <title>聚合根类</title>
    </head>
    <body>
        <script>
            /*
                        二、聚合根类 AggregateRoot Class

                                    聚合是域驱动设计中的一种模式。DDD聚合是可以被视为单个单元的域对象集群。

                                    一个示例可能是订单及其行项，它们将是单独的对象，但将订单（及其行项）视为单个聚合很有用。"（请参阅完整描述）

                                    ★、请注意，默认情况下，ABP只为聚合根创建默认存储库。但是，可以包含所有实体。有关详细信息，请参阅存储库留档。

                                    ABP不强制你使用聚合根，你实际上可以使用之前定义的Entity类。

                                    但是，如果你想实现域驱动设计并想创建聚合根类，你可能需要考虑一些最佳实践：

                                           ①、聚合根负责保持其自身的完整性。这对于所有实体也是如此，但聚合根也对其子实体负责。因此，聚合根必须始终处于有效状态。 

                                           ②、聚合根可以由其Id引用。不要通过其导航属性引用它。

                                           ③、聚合根被视为单个单元。它作为单个单元检索和更新。它通常被认为是一个"事务边界"。

                                           ④、在聚合根上使用子实体——不要独立修改它们。

                                    如果你想在应用程序中实现DDD，请参阅实体设计最佳实践指南。


                                    1、聚合示例  Aggregate Example

                                                public class Order : AggregateRoot<Guid>
                                                {
                                                    public virtual string ReferenceNo { get; protected set; }

                                                    public virtual int TotalItemCount { get; protected set; }

                                                    public virtual DateTime CreationTime { get; protected set; }

                                                    public virtual List<OrderLine> OrderLines { get; protected set; }

                                                    protected Order()
                                                    {

                                                    }

                                                    public Order(Guid id, string referenceNo)
                                                    {
                                                        Check.NotNull(referenceNo, nameof(referenceNo));
                                                        
                                                        Id = id;
                                                        ReferenceNo = referenceNo;
                                                        
                                                        OrderLines = new List<OrderLine>();
                                                    }

                                                    public void AddProduct(Guid productId, int count)
                                                    {
                                                        if (count <= 0)
                                                        {
                                                            throw new ArgumentException(
                                                                "You can not add zero or negative count of products!",
                                                                nameof(count)
                                                            );
                                                        }

                                                        var existingLine = OrderLines.FirstOrDefault(ol => ol.ProductId == productId);

                                                        if (existingLine == null)
                                                        {
                                                            OrderLines.Add(new OrderLine(this.Id, productId, count));
                                                        }
                                                        else
                                                        {
                                                            existingLine.ChangeCount(existingLine.Count + count);
                                                        }

                                                        TotalItemCount += count;
                                                    }
                                                }

                                                public class OrderLine : Entity
                                                {
                                                    public virtual Guid OrderId { get; protected set; }

                                                    public virtual Guid ProductId { get; protected set; }

                                                    public virtual int Count { get; protected set; }

                                                    protected OrderLine()
                                                    {

                                                    }

                                                    internal OrderLine(Guid orderId, Guid productId, int count)
                                                    {
                                                        OrderId = orderId;
                                                        ProductId = productId;
                                                        Count = count;
                                                    }

                                                    internal void ChangeCount(int newCount)
                                                    {
                                                        Count = newCount;
                                                    }
                                                    
                                                    // 定义复合主键
                                                    public override object[] GetKeys()
                                                    {
                                                        return new Object[] {OrderId, ProductId};
                                                    }
                                                }

                                            果不想从基AggregateRoot<TKey>类派生聚合根，可以直接实现IAggregateRoot<TKey>接口。

                                            Order是具有GuidtypeId属性的聚合根。它具有OrderLine实体的集合。OrderLine是另一个具有复合主键（OrderId和 ProductId）的实体。 
                                            
                                            然此示例可能无法实现聚合根的所有最佳实践，但它仍然遵循一些良好实践：

                                                ①、 Order有一个公共构造函数，它需要最低要求来构造Order实例。
                                                    
                                                    因此，如果没有id和引用号，就不可能创建订单。protected/私有构造函数只需要在从数据源读取时反序列化对象。

                                                ②、OrderLine构造函数是内部的，因此只允许由领域层创建。它在Order.AddProduct方法内部使用。

                                                ③、Order.AddProduct实现将产品添加到订单的业务规则

                                                ④、所有属性都有protectedsetter。这是为了防止实体从实体外部进行任意更改。

                                                            例如，在不向订单添加新产品的情况下设置TotalItemCount是危险的。它的值由AddProduct方法维护。

                                            ABP不会强迫你应用任何DDD规则或模式。但是，当你确实想要应用它们时，它会努力使其成为可能和更容易。留档也遵循相同的原则。


                                    2、复合主键聚合根

                                            虽然聚合根并不常见（也不建议），但实际上可以以与上述实体相同的方式定义复合键。在这种情况下使用非泛型AggregateRoot基类。



                                    3、基础聚合根基类  BasicAggregateRoot 基类

                                            AggregateRoot类实现了IHasExtraProperties和IHasConcurrencyStamp接口，这为派生类带来了两个属性

                                            IHasExtraProperties使实体可扩展（请参阅下面的额外属性部分），
                                            
                                            IHasConcurrencyStamp添加了一个由ABP管理的ConcurrencyStamp属性，以实现乐观并发。

                                            在大多数情况下，这些是聚合根所需的特性。

                                            但是，如果你不需要这些功能，你可以从BasicAggregateRoot<TKey>（或BasicAggregateRoot）继承聚合根
                    */
        </script>
    </body>
</html>